﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ARR.Entities;
using ARR.DataModel.Repositories;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;
using ARR.Entities.Gateways;
using DDF.Entity;
using System.Transactions;
using System.ServiceModel;
using ARR.Shared;


namespace ARR.Services.Domain
{
    public static class ArticlesDomain
    {
        #region Articles
        //Articles Domain
        public static void UpdateStatus(int rangeReviewId)
        {
            var rangeReview = RangeReviewsRepository.Select(x => x.Id == rangeReviewId).FirstOrDefault();
            if (rangeReview != null)
            {
                var country = CountriesRepository.Select(x => x.Id == rangeReview.CountryId).FirstOrDefault();
                if (country != null)
                {
                    //Actualizo los Status de los Articles segun la base de ventas.
                    if (country.IsArticleMasterSeparateFile)
                    {
                        ArticlesRepository.UpdateArticlesStatus(rangeReviewId);
                    }
                    else
                    {
                        ArticlesRepository.UpdateStatus(rangeReviewId);
                    }
                }
            }
        }

        public static List<Article> GetAll(int rangeReviewId, int? categoryId)
        {
            List<ArticleRow> allArticles = null;
            if (categoryId.HasValue && categoryId != 0)
            {
                var categories = CategoriesRepository.Select(x => (x.Id == categoryId.Value || x.ParentId == categoryId.Value) && x.Level == 3);

                var articles1 = ArticlesRepository.Select(x => x.RangeReviewId == rangeReviewId);

                allArticles = (from a in articles1
                               join c in categories on a.CategoryId equals c.Id
                               select a).ToList();
            }
            else
            {
                allArticles = ArticlesRepository.Select(x => x.RangeReviewId == rangeReviewId);
            }

            List<Article> articles = allArticles.ConvertAll<Article>(art => new Article(art));

            articles.ForEach(art => art.ChangeState(EntityState.Original));

            return articles;
        }

        public static List<Article> SearchArticles(string criteria, int rangeReviewId)
        {
            var categories = CategoriesDomain.GetAll();

            List<Article> allArticles = ArticlesRepository.Select(x => x.RangeReviewId == rangeReviewId).ConvertAll<Article>(row => new Article(row)).ToList();
            allArticles.ForEach(x => x.Category = categories.SingleOrDefault(c => c.Id == x.CategoryId));


            List<Article> foundArticles = allArticles.Where(x => x.Code.ToLower().Contains(criteria.ToLower()) ||
                                                                 x.Name.ToLower().Contains(criteria.ToLower()) ||
                                                                 x.Category.Name.ToLower().Contains(criteria.ToLower())).ToList();

            foundArticles.ForEach(art => art.ChangeState(EntityState.Original));

            return foundArticles;
        }

        public static List<Article> GetAllComplete(int rangeReviewId, int? categoryId)
        {
            List<Article> articles = GetAll(rangeReviewId, categoryId);

            var categories = CategoriesDomain.GetAll();
            var uomConvertions = UnitsOfMeasureDomain.GetAll();
            var attributes = AttributesRepository.Select();
            var artAttributes = ArticleAttributesRepository.Select();
            var artComments = CommentsDomain.GetByType("Articles");

            foreach (Article art in articles)
            {
                art.Category = categories.SingleOrDefault(cat => cat.Id == art.CategoryId);
                if (art.UnitOfMeasureConvertionId.HasValue)
                {
                    art.UomConversion = uomConvertions.SingleOrDefault(uom => uom.Id == art.UnitOfMeasureConvertionId);
                }

                var ArticleAttributes = from aa in artAttributes
                                        join att in attributes on aa.AttributeId equals att.Id
                                        where aa.ArticleId == art.Id
                                        select new Entities.Attribute(att);

                art.Attributes = ArticleAttributes.ToList();
                if (art.CommentId.HasValue)
                {
                    art.Comment = artComments.Where(x => x.Id == art.CommentId).FirstOrDefault();
                }

                art.ChangeState(EntityState.Original);
            }

            return articles.ToList();
        }

        public static Article GetByID(int articleID)
        {
            var articleRow = ArticlesRepository.Select(art => art.Id == articleID).SingleOrDefault();

            return new Article(articleRow);
        }

        public static void Join(int[] articleIds, int targetArticleId)
        {
            //TODO: por ahora puse todos los updates dentro del mismo Stored Procedure. Ver de separar y meter en una transaccion
            ArticlesRepository.Join(articleIds, targetArticleId);
        }

        public static void Split(int sourceArticleId, List<Article> articles)
        {
            //TODO: por ahora puse todos los updates dentro del mismo Stored Procedure 'Article_Split'. Ver de separar.
            try
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    bool found = false;

                    List<ArticleRow> rows = new List<ArticleRow>();

                    foreach (Article article in articles)
                    {
                        if (article.Id == sourceArticleId)
                            found = true;

                        SaveArticle(article);

                        rows.AddRange(ArticlesRepository.Select(x => x.Id == article.Id));
                    }

                    if (!found)
                    {
                        Article originalArticle = GetByID(sourceArticleId);
                        originalArticle.IsDeletedPerformance = true;
                        originalArticle.IsDeletedRangeReview = true;

                        SaveArticle(originalArticle);
                    }

                    var ids = articles.Select(art => art.Id).ToArray();

                    ArticlesRepository.Split(sourceArticleId, ids);

                    scope.Complete();
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public static void SaveArticles(List<Article> articles)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                List<Article> toBeSaved = articles.Where(t => t.GetEntityState() != EntityState.Original).ToList();

                foreach (Article article in toBeSaved)
                {
                    SaveArticle(article);
                }

                scope.Complete();
            }
        }

        private static void SaveArticle(Article article)
        {
            if (article.IsValid)
            {
                if (article.GetEntityState() == EntityState.New)
                {
                    ArticlesRepository.Insert(article.GetRow());
                }
                else if (article.GetEntityState() == EntityState.Modified)
                {
                    ArticlesRepository.Update(article.GetRow());
                }

                ArticleAttributesRepository.Delete(a => a.ArticleId == article.Id);

                ArticleAttribute articleAtt;
                if (article.Attributes != null)
                {
                    foreach (Entities.Attribute att in article.Attributes)
                    {
                        articleAtt = new ArticleAttribute
                        {
                            ArticleId = article.Id,
                            AttributeId = att.Id
                        };

                        ArticleAttributesRepository.Insert(articleAtt.GetRow());
                    }
                }
            }
            else
            {
                throw new FaultException(string.Format(Errors.CanNotSaveArticleBecauseOf, article.Name, article.Error));
            }
        }

        //TempArticles Domain
        public static List<TempArticle> GetTempArticlesByIds(IEnumerable<int> listIds)
        {
            var tempArticleRows = TempArticlesRepository.Select();

            var articles = tempArticleRows.Where(tmpA => listIds.Contains(tmpA.Id))
                                          .ToList()
                                          .ConvertAll<TempArticle>(row => new TempArticle(row));

            articles.ForEach(a => a.ChangeState(EntityState.Original));

            return articles;
        }

        public static void SaveTempArticles(List<TempArticle> tempArticles)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                List<TempArticle> modifieds = tempArticles.Where(t => t.GetEntityState() == EntityState.Modified).ToList();
                //var sales = TempArticleSalesRepository.Select(x => x.CountryId == modifieds.First().CountryId);

                foreach (TempArticle tmp in modifieds)
                {
                    //TODO: Llevar strings a Recursos.
                    //Creo registro de auditoria de los cambios realizados.
                    StringBuilder auditLog = new StringBuilder("Se han actualizado las siguientes propiedades del artículo '{0}': ");
                    var original = TempArticlesRepository.Select(x => x.Id == tmp.Id).SingleOrDefault();

                    if (original.Name != tmp.Name)
                        auditLog.AppendFormat("Name: {0}->{1}  ", original.Name, tmp.Name);

                    if (original.CategoryId != tmp.CategoryId)
                        auditLog.AppendFormat("CategoryId: {0}->{1}  ", original.CategoryId, tmp.CategoryId);

                    if (original.EAN != tmp.EAN)
                        auditLog.AppendFormat("EAN: {0}->{1}  ", original.EAN, tmp.EAN);

                    if (original.UOM != tmp.UOM)
                        auditLog.AppendFormat("UOM: {0}->{1}  ", original.UOM, tmp.UOM);

                    if (original.Brand != tmp.Brand)
                        auditLog.AppendFormat("Brand: {0}->{1}  ", original.Brand, tmp.Brand);

                    if (original.Manufacturer != tmp.Manufacturer)
                        auditLog.AppendFormat("Manufacturer: {0}->{1}  ", original.Manufacturer, tmp.Manufacturer);

                    var auditTrail = new AuditTrail
                    {
                        Comments = auditLog.ToString(),
                        Date = DateTime.Now,
                        Source = "UI:'Importador de Artículos'"
                    };

                    AuditTrailsRepository.Insert(auditTrail.GetRow());

                    TempArticlesRepository.Update(tmp.GetRow());

                    //Actualizo también los TempArticleSales por si estoy corriendo el importador unificado
                    //TempArticleSalesRepository.Update(tmp.GetRow());

                }


                scope.Complete();
            }
        }

        public static void CombineRepeatedNamesAndUpdateSales(List<TempArticle> repeatedArticles)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                List<string> codes = new List<string>();
                foreach (var art in repeatedArticles)
                {
                    if (string.IsNullOrEmpty(art.NewCode))
                        throw new ArgumentNullException("article.NewCode");

                    codes.Add(art.Code + "," + art.NewCode);
                }

                //Marco las ventas anteriores como confirmed = false. 
                //Inserto nuevos regisros con las ventas sumarizadas.
                TempArticlesRepository.CombineRepeatedNamesAndUpdateSales(codes.ToArray());

                //Elimino los articulos repetidos.
                DeleteTempArticles(repeatedArticles);

                //Genero los registros de auditoria.
                List<AuditTrail> auditTrails = new List<AuditTrail>();
                repeatedArticles.ForEach(art =>
                                        {
                                            auditTrails.Add(new AuditTrail
                                            {
                                                Source = "Importador",
                                                ErrorType = "Articulo con nombre repetido",
                                                Date = DateTime.Now,
                                                Comments = string.Format("El articulo {0} con código {1}, ha sido reemplazado por el artículo código {2}.", art.Name, art.Code, art.NewCode)
                                            });
                                        });

                AuditTrailsDomain.SaveAuditTrails(auditTrails);


                scope.Complete();
            }
        }

        public static void DeleteTempArticles(List<TempArticle> tempArticles)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                foreach (var art in tempArticles)
                {
                    TempArticlesRepository.Delete(t => t.Id == art.Id);
                }

                scope.Complete();
            }
        }

        public static void UpdateTempArticlesWithCategoryMatch(int categoryId, string[] originalCategories)
        {
            TempArticlesRepository.UpdateCategoryMatch(categoryId, originalCategories);
        }

        public static List<TempArticle> GetSameStoreArticles(int sameStoreId)
        {
            var rows = TempArticlesRepository.GetSameStoreArticles(sameStoreId);

            List<TempArticle> articles = new List<TempArticle>();

            rows.ForEach(row => articles.Add(new TempArticle(row)));

            return articles;
        } 
        #endregion
        
        #region ArticleSales

        //ArticleSales
        
        //OJO! con este, si hay muchas ventas tira out of memory exception con facilidad. (Solo si lo llamas desde el servicio.)
        public static List<ArticleSale> GetAllSales(int rangeReviewId, int? categoryId = null)
        {
            var sales = ArticleSalesRepository.SelectByRangeReview(rangeReviewId, categoryId).ConvertAll<ArticleSale>(row => new ArticleSale(row));

            sales.ForEach(x => x.ChangeState(EntityState.Original));

            return sales;
        }

        public static void SaveSales(List<ArticleSale> sales)
        {
            using(TransactionScope scope = new TransactionScope())
            {
                foreach(ArticleSale sale in sales)
                {
                    if(sale.GetEntityState() == EntityState.New)
                    {
                        ArticleSalesRepository.Insert(sale.GetRow());
                    }
                    else if(sale.GetEntityState() == EntityState.Modified)
                    {
                        ArticleSalesRepository.Update(sale.GetRow());
                    }
                }

                scope.Complete();
            }
        }

        public static List<CategoryMargin> GetCategoryMargins(int rangeReviewId)
        {
            var margins = ArticleSalesRepository.SelectCategoryMargins(rangeReviewId).ConvertAll<CategoryMargin>(row => new CategoryMargin(row));

            return margins;
        }


        //TempArticleSales
        public static List<TempArticleSale> GetTempArticleSalesDuplicates()
        {
            var sales = TempArticleSalesRepository.SelectDuplicates().ConvertAll<TempArticleSale>(row => new TempArticleSale(row));

            sales.ForEach(x => x.ChangeState(EntityState.Original));

            return sales;
        }

        public static List<TempArticleSale> GetTempArticleSalesByIds(IEnumerable<int> listIds)
        {
            var rows = TempArticleSalesRepository.SelectByIdList(listIds.ToArray());

            List<TempArticleSale> sales = new List<TempArticleSale>();

            rows.ForEach(row => sales.Add(new TempArticleSale(row)));

            sales.ForEach(s => s.ChangeState(EntityState.Original));

            return sales;
        }

        public static void UpdateTempArticleSalesArticleIds(int rangeReviewId)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                TempArticleSalesRepository.UpdateArticleIds(rangeReviewId);
                scope.Complete();
            }
        }

        public static void UpdateTempArticleSalesStoreIds()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                TempArticleSalesRepository.UpdateStoreIds();
                scope.Complete();
            }
        } 

        //TempPIDifferences
        public static List<TempPIDifference> GetTempPIDifferencesByIds(IEnumerable<int> listIds)
        {
            var rows = TempPIDifferencesRepository.SelectByIdList(listIds.ToArray());

            List<TempPIDifference> diffs = new List<TempPIDifference>();

            rows.ForEach(row => diffs.Add(new TempPIDifference(row)));

            diffs.ForEach(s => s.ChangeState(EntityState.Original));

            return diffs;
        }

        public static List<TempPIDifference> GetTempPIDifferencesDuplicates()
        {
            var sales = TempPIDifferencesRepository.SelectDuplicates().ConvertAll<TempPIDifference>(row => new TempPIDifference(row));

            sales.ForEach(x => x.ChangeState(EntityState.Original));

            return sales;
        }

        public static void SaveTempPIDifferences(List<TempPIDifference> diffs)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                foreach (TempPIDifference diff in diffs)
                {
                    if (diff.GetEntityState() == EntityState.New)
                    {
                        TempPIDifferencesRepository.Insert(diff.GetRow());
                    }
                    else if (diff.GetEntityState() == EntityState.Modified)
                    {
                        TempPIDifferencesRepository.Update(diff.GetRow());
                    }
                }

                scope.Complete();
            }
        }
        
        #endregion

    }
}
